package org.windowkit.toolkit.widget.treetable;

import java.awt.EventQueue;
import java.util.ArrayList;

import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;

import org.ddevil.data.Data;
import org.ddevil.data.DisplayDescriptor;
import org.ddevil.data.set.SortableDataSet;


/**
 * This table model delegates to the tree table model for its data and table
 * structure. It also delegates tree events to the table in a table model event
 * so the table can understand what happened.
 *
 * @author Eric Lundin
 */
@SuppressWarnings("serial")
public class TreeTableModelAdapter extends AbstractTableModel {

	TreeTableModel treeTableModel;

	JTree tree;

	TreeToTableEventDispatcher dispatcher;

	public TreeTableModelAdapter(final TreeTableModel model, final JTree theTree) {
		tree = theTree;
		dispatcher = new TreeToTableEventDispatcher();
		setTreeModel(model);
		tree.addTreeExpansionListener(dispatcher);
		tree.addTreeWillExpandListener(dispatcher);
	}

	public void setTreeModel(final TreeTableModel model) {
		treeTableModel = model;
		treeTableModel.addTreeModelListener(dispatcher);
		fireTableStructureChanged();
	}

	public int getRowCount() {
		return tree.getRowCount();
	}

	protected Object nodeForRow(final int row) {
		TreePath treePath = tree.getPathForRow(row);
		return treePath.getLastPathComponent();
	}

	public int getColumnCount() {
		return treeTableModel.getColumnCount();
	}

	@Override
	public String getColumnName(final int column) {
		return treeTableModel.getColumnName(column);
	}

	/**
	 * Hack to get the table to delegate all rendering to the tree table
	 * renderer which will call back to the table for the true default renderer
	 * if the row does not need to be drawn with a tree.
	 */
	@Override
	@SuppressWarnings("unused")
	public Class< ? extends Object> getColumnClass(final int column) {
		return TreeTableModel.class;
	}

	public Object getValueAt(final int row, final int column) {
		return treeTableModel.getValueAt(nodeForRow(row), column);
	}

	@Override
	public boolean isCellEditable(final int row, final int column) {
		return treeTableModel.isCellEditable(nodeForRow(row), column);
	}

	@Override
	public void setValueAt(final Object value, final int row, final int column) {
		treeTableModel.setValueAt(value, nodeForRow(row), column);
	}

	public void setChangeExportKeys(final ArrayList<ArrayList<String>> exportAttribs) {
		treeTableModel.setChangeExportKeys(exportAttribs);
	}

	public void setData(final SortableDataSet data) {
		treeTableModel.setData(data);
	}

	public void setLabelsAndKeys(final DisplayDescriptor labels,
			final ArrayList<ArrayList<String>> keys) {
		treeTableModel.setLabelsAndKeys(labels, keys);
		fireTableStructureChanged();
	}

	/**
	 * @author Eric Lundin
	 */
	final class TreeToTableEventDispatcher implements TreeModelListener,
			TreeExpansionListener, TreeWillExpandListener {
		@SuppressWarnings("unused")
		public void treeNodesChanged(final TreeModelEvent e) {
			fireTableDataChangedLater();
		}

		@SuppressWarnings("unused")
		public void treeNodesInserted(final TreeModelEvent e) {
			fireTableDataChangedLater();
		}

		@SuppressWarnings("unused")
		public void treeNodesRemoved(final TreeModelEvent e) {
			fireTableDataChangedLater();
		}

		@SuppressWarnings("unused")
		public void treeStructureChanged(final TreeModelEvent e) {
			fireTableDataChangedLater();
		}

		@SuppressWarnings("unused")
		public void treeExpanded(final TreeExpansionEvent event) {
			fireTableDataChanged();
		}

		@SuppressWarnings("unused")
		public void treeCollapsed(final TreeExpansionEvent event) {
			fireTableDataChanged();
		}

		/**
		 * Invokes fireTableDataChanged after all the pending events have been
		 * processed. This way any tree updates should get executed first so the
		 * table displays the correct data.
		 */
		protected void fireTableDataChangedLater() {
			EventQueue.invokeLater(new Runnable() {
				public void run() {
					fireTableDataChanged();
				}
			});
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TreeWillExpandListener#treeWillCollapse(javax.swing.event.TreeExpansionEvent)
		 */
		@SuppressWarnings("unused")
		public void treeWillCollapse(final TreeExpansionEvent event)
				throws ExpandVetoException {
			// Not used, maybe in future?
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.TreeWillExpandListener#treeWillExpand(javax.swing.event.TreeExpansionEvent)
		 */
		@SuppressWarnings("unused")
		public void treeWillExpand(final TreeExpansionEvent event)
				throws ExpandVetoException {
			// Not used, maybe in future?
		}
	}

	/**
	 * @param ds
	 */
	// public void addRecord(DataSet ds) {
	// treeTableModel.addRecord(ds);
	// }
	/**
	 * @param record
	 */
	public void addRecord(final Data record) {
		treeTableModel.addRecord(record);
	}

	/**
	 *
	 */
	public void clearData() {
		treeTableModel.clearData();
	}

	/**
	 *
	 */
	public DisplayDescriptor getDisplayDescriptor() {
		return treeTableModel.getDisplayDescriptor();
	}

	/**
	 * @param editableColumns
	 */
	public void setEditableColumns(final ArrayList<String> editableColumns) {
		treeTableModel.setEditableColumns(editableColumns);
	}

	/**
	 * @param dbId
	 */
	public int getIndexOfId(final String dbId) {
		return treeTableModel.getIndexOfId(dbId);
	}

	public void setAutoClearChangeTracker(final boolean auto) {
		treeTableModel.setAutoClearChangeTracker(auto);
	}

	/**
	 *
	 */
	public void clearChanges() {
		treeTableModel.clearChanges();
	}

	/**
	 * @param oldValue
	 * @param newValue
	 */
	public boolean modifyRecord(final Data oldValue, final String dbId,
			final Object value, final int level) {
		return treeTableModel.modifyRecord(oldValue, dbId, value, level);
	}

}
